use std::ops::Deref;

use bytes::{BufMut, Bytes, BytesMut};
use reqwest::{Client, Response, StatusCode};
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use tokio::sync::Mutex;
use futures_util::StreamExt;

use crate::error::AppErr;

const BASE_URL: &'static str = "http://local.hontech-rdcenter.com:23331/api";


#[derive(Serialize)]
struct LoginReq {
    account: &'static str,
    pwd: &'static str,
}

#[derive(Debug, Deserialize)]
struct ErrResp {
    err_code: i32,
    err_msg: String,
}

#[derive(Debug, Deserialize)]
struct User {
    id: i64,
    name: String,
    account: String,
    pwd_enc: String,
}

#[derive(Debug, Deserialize)]
struct LoginRes {
    user: User,
    token: String,
}

#[derive(Deserialize, Serialize)]
pub struct FilmFile {
    pub id: i64,
    pub description: String,
    pub version: String,
    pub size: usize,
    pub create_timestamp: u64,
}

static LOGIN_RES: Mutex<Option<LoginRes>> = Mutex::const_new(None);

async fn parse_resp<T: DeserializeOwned>(resp: Response) -> Result<Response, AppErr> {

    if resp.status() != StatusCode::OK {
        return Err(AppErr::Static("http not ok"));
    }

    let resp_head = resp.headers()
        .get("resp")
        .ok_or(AppErr::Static("not resp"))?;

    if resp_head != "ok" {
        let err_resp: ErrResp = resp.json().await?;
        return Err(AppErr::HttpFilm(err_resp.err_code));
    }

    Ok(resp)
}

async fn login(client: &Client) -> Result<(), AppErr> {
    let req = LoginReq {
        account: "device",
        pwd: "device1234"
    };
    let body = serde_json::to_vec(&req)?;
    let resp = client
        .post(format!("{}/user/login", BASE_URL))
        .body(body)
        .send()
        .await?;
    let login_res: LoginRes = resp.json().await?;
    *LOGIN_RES.lock().await = Some(login_res);
    Ok(())
}

async fn get_token(client: &Client) -> Result<String, AppErr> {

    let login_res = LOGIN_RES.lock().await;
    if let Some(res) = login_res.deref() {
        return Ok(res.token.clone());
    }
    drop(login_res);

    login(client).await?;

    let login_res = LOGIN_RES.lock().await;
    if let Some(res) = login_res.deref() {
        Ok(res.token.clone())
    } else {
        Err(AppErr::Static("no token"))
    }
}

async fn do_get_once(client: &Client, url: &str) -> Result<Response, AppErr> {
   let token = get_token(client).await?;
    let url = format!("{}{}", BASE_URL, url);
    let client = Client::new();
    let resp = client.get(url)
        .header("token", &token)
        .send().await?;
    Ok(resp)
}


async fn do_get(url: &str) -> Result<Response, AppErr> {
    let client = Client::new();

    match do_get_once(&client, url).await {
        Ok(v) => return Ok(v),
        Err(AppErr::HttpFilm(_)) => {},
        Err(e) => return Err(e),
    };
    login(&client).await?;
    do_get_once(&client, url).await
}

pub async fn query(type_id: i64) -> Result<Vec<FilmFile>, AppErr> {
    let resp = do_get(&format!("/dev_file/query?type_id={}", type_id)).await?;
    Ok( resp.json().await? )
}

// /dev_file/query?type_id=22

pub async fn download_apk<F: Fn(u32)>(type_id: i64, cb: F) -> Result<Bytes, AppErr> {
    let resp = do_get(&format!("/dev_file/get?id={}", type_id)).await?;
    let content_length = resp.content_length().ok_or(AppErr::Static("no content length"))? as usize;
    let mut stream = resp.bytes_stream();
    let mut buf = BytesMut::with_capacity(content_length);
    let mut index = 0;
    while let Some(chunk) = stream.next().await {
        let chunk = chunk?;
        buf.extend_from_slice(&chunk);
        index += chunk.len();
        cb( (index * 100 / content_length) as u32 );
    }
    Ok(buf.freeze())
}














